package com.xunk.equipment.interceptor;

import cn.hutool.core.util.ObjectUtil;
import com.xunk.common.core.utils.ServletUtils;
import com.xunk.common.core.utils.ThreadLocalUtils;
import com.xunk.common.core.utils.XunkAssert;
import com.xunk.common.core.utils.serialization.JsonSerializer;
import com.xunk.common.redis.service.RedisService;
import com.xunk.common.security.utils.SecurityUtils;
import com.xunk.equipment.annotation.OperationLog;
import com.xunk.equipment.domain.DeviceControlLog;
import com.xunk.equipment.domain.EquipmentDO;
import com.xunk.equipment.mapper.EquipmentMapper;
import com.xunk.equipment.mapper.HomeMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * @ProjectName: xunk
 * @Package: com.xunk.equipment.interceptor
 * @ClassName: DeviceControlAspect
 * @Author: yangwendong
 * @Description:
 * @Date: 2021/4/12 14:31
 * @Version: 1.0
 */
@Aspect
@Component
@Slf4j
public class DeviceControlAspect {

    @Autowired
    EquipmentMapper equipmentMapper;

    @Autowired
    HomeMapper homeMapper;

    @Resource(name = "asyncServiceTaskExecutor")
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    RedisService redisService;

    private final static String SCENE_KEY = "scene:sceneKey";
    private final static String USER_ID = "userId";

    @Pointcut(value = "execution(* com.xunk.equipment.service.IEquipmentService.control(..)) || execution(* com.xunk.equipment.service.IEquipmentService.getEquipmentInfo(..))")
    public void point() {
    }

    @Pointcut("@annotation(com.xunk.equipment.annotation.OperationLog)")
    public void operation() {
    }

    @Around(value = "point()")
    public Object deviceControlLog(ProceedingJoinPoint jp) throws Throwable {
        // 控制前校验身份
        Object[] args = jp.getArgs();
        EquipmentDO equipmentDO = (EquipmentDO) args[0];
        Long userId = ThreadLocalUtils.get(USER_ID);
        if (Objects.isNull(userId)) {
            userId = SecurityUtils.getUserId(false);
        }
        if (Objects.isNull(userId)) {
            // 如果userId为空，可能来源是场景模块，校验sceneKey
            String sceneKey = redisService.getCacheObject(SCENE_KEY);
            String sceneKeyH = ServletUtils.getRequest().getHeader("sceneKey");
            log.info("header -------- sceneKey : {}, redisKey : {}", sceneKeyH, sceneKey);
            XunkAssert.isTrue(Objects.equals(sceneKey, sceneKeyH), "非法用户操作设备请求");
            // 0系统用户
            userId = 0L;
        } else {
            // 校验当前用户和设备归属的关系
            List<Long> userIds = homeMapper.listUserIdsByDeptId(equipmentDO.getDeptId());
            XunkAssert.isTrue(userIds.contains(userId), "非法用户操作设备请求");
        }
        Long finalUserId = userId;
        Object result = jp.proceed();
        Signature signature = jp.getSignature();
        // 控制(发送)成功后记录操作日志，TODO 并且发送到场景模块
        if (Objects.equals("control", signature.getName())) {
            CompletableFuture.runAsync(() -> {
                String type = (String) args[1];
                String value = (String) args[2];
                EquipmentDO equipment = equipmentMapper.getEquipment(equipmentDO.getDeviceSn());
                DeviceControlLog log = new DeviceControlLog();
                log.setUserId(finalUserId);
                log.setDeptId(equipment.getDeptId());
                log.setDeviceSn(equipment.getDeviceSn());
                log.setDeviceName(equipment.getDeviceName());
                log.setDeviceType(equipment.getDeviceType());
                log.setSupplier(equipment.getSupplier());
                log.setDeviceOnline(equipment.getDeviceOnline() + "");
                log.setControlType(type);
                log.setControlValue(value);
                log.setCreateTime(LocalDateTime.now());
                equipmentMapper.insertDeviceControlLog(log);
            }, threadPoolTaskExecutor).exceptionally(e -> {
                log.error("保存设备操作动作日志异常", e);
                return null;
            });
        }
        return result;
    }

    /**
     * @param joinPoint
     */
    @AfterReturning(pointcut = "operation()")
    public void operationLog(JoinPoint joinPoint) {
        Long userId = SecurityUtils.getUserId();
        CompletableFuture.runAsync(() -> {
            Object[] args = joinPoint.getArgs();
            String deviceSn = JsonSerializer.serialize(args[0]);
            EquipmentDO equipment = equipmentMapper.getEquipment(deviceSn);
            if (ObjectUtil.isEmpty(equipment)) {
                log.info("未查询到该设备，未记录操作日志，设备id为：{},用户id为：{}", deviceSn, userId);
                return;
            }
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            OperationLog annotation = method.getAnnotation(OperationLog.class);
            DeviceControlLog log = new DeviceControlLog();
            log.setUserId(userId);
            log.setDeptId(equipment.getDeptId());
            log.setDeviceSn(equipment.getDeviceSn());
            log.setDeviceName(equipment.getDeviceName());
            log.setDeviceType(equipment.getDeviceType());
            log.setSupplier(equipment.getSupplier());
            log.setDeviceOnline(equipment.getDeviceOnline() + "");
            //记录操作描述
            log.setControlType(annotation.operateDesc());
            //记录接口入参类型
            log.setControlValue(JsonSerializer.serialize(args));
            log.setCreateTime(LocalDateTime.now());
            equipmentMapper.insertDeviceControlLog(log);
        }, threadPoolTaskExecutor).exceptionally(e -> {
            log.error("保存设备操作动作日志异常", e);
            return null;
        });


    }
}
